package lsp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;

import api.LspParams;

public class InPackageHandlerServer implements Runnable {
	private LinkedBlockingDeque<LspMessage> entrada;
	private int port;
	private DatagramSocket ds;
	private ConcurrentHashMap<Short, LspConnection> tabelaDeConexoes;
	private LspParams params;
	private short connId = 0;

	public InPackageHandlerServer(LinkedBlockingDeque<LspMessage> entrada,
			int port, ConcurrentHashMap<Short, LspConnection> tabelaDeConexoes,
			LspParams params) throws SocketException {
		// this.ds = tabelaDeConexoes;
		this.entrada = entrada;
		this.port = port;
		this.tabelaDeConexoes = tabelaDeConexoes;
		this.params = params;
		this.ds = new DatagramSocket(this.port);
	}

	@Override
	public void run() {
		byte[] mensagemRecebida;
		DatagramPacket pacote;
		LspMessage mensagem;
		try {
			while (!Thread.currentThread().isInterrupted()) {
				mensagemRecebida = new byte[1000];
				pacote = new DatagramPacket(mensagemRecebida,
						mensagemRecebida.length);
				this.ds.receive(pacote);
				mensagem = new LspMessage(pacote.getData());
				if (mensagem.getMsgType() == 0) // Connect
					recebePedidoConexao(mensagem, pacote.getAddress(),
							pacote.getPort());
				else if (mensagem.getMsgType() == 1) // Dados
					recebeData(mensagem);
				else if (mensagem.getMsgType() == 2) // Ack
					recebeAck(mensagem);
			}
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
			Thread.currentThread().interrupt();
		}
	}

	private void recebeAck(LspMessage mensagemRecebida)
			throws InterruptedException {
		short identificador = mensagemRecebida.getConnId();
		if (this.tabelaDeConexoes.containsKey(identificador)) {
			LspConnection conexao = this.tabelaDeConexoes.get(identificador);
			conexao.recebeAck(mensagemRecebida); // Envia Ack
		}

	}

	private void recebeData(LspMessage mensagemRecebida)
			throws InterruptedException, IOException {
		short identificador = mensagemRecebida.getConnId();
		if (this.tabelaDeConexoes.containsKey(identificador)) {
			LspConnection conexao = this.tabelaDeConexoes.get(identificador);
			if (conexao.recebeData(mensagemRecebida))
				this.entrada.putFirst(mensagemRecebida);
		}
	}

	private void recebePedidoConexao(LspMessage mensagemRecebida,
			InetAddress cliente, int porta) throws InterruptedException {
		if (!this.containsClient(cliente, porta)) {
			short identificador = this.nextConnId();
			LspConnection conexao = new LspConnection(this.ds, identificador,
					this.params, cliente, porta);
			// conexao.recebeConnect(mensagemRecebida);
			this.tabelaDeConexoes.put(identificador, conexao);
			conexao.enviaAck(new LspMessage((short) 2, identificador,
					mensagemRecebida.getSeqNumber(), "".getBytes())); // Envia
																		// Ack

			// Thread lspConnection = new Thread(conexao);
			// lspConnection.start();
		}

		// this.epochLimitExpired.set(false); // Reinicia o contador
	}

	private short nextConnId() {
		if (this.connId  == (Short.MAX_VALUE - Short.MIN_VALUE))
			this.connId = 1;
		else
			this.connId = (short) (this.connId+1);
		return this.connId;
	}

	private boolean containsClient(InetAddress cliente, int porta) {
		for (Entry<Short, LspConnection> elemento : this.tabelaDeConexoes
				.entrySet()) {
			LspConnection conexao = elemento.getValue();
			if (conexao.getClient().getHostAddress()
					.equals(cliente.getHostAddress())
					&& (conexao.getPort() == porta))
				return true;
		}
		return false;
	}

//	private void cleanDiedClient() {
//		for (Entry<Short, LspConnection> elemento : this.tabelaDeConexoes
//				.entrySet()) {
//			LspConnection conexao = elemento.getValue();
//			if (conexao.getEpochLimitExpired().get())
//				this.tabelaDeConexoes.remove(elemento.getKey());
//		}
//	}
}
